﻿using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

public interface IPoolItem<T> : IDisposable
{
    void Copy(T t);
}

public interface IPool
{
    void DestroyPool();
    void CheckShrink();
}

public class ObjectPoolManager : Manager<ObjectPoolManager>, ISlowUpdate
{
    private List<IPool> m_poolList = new List<IPool>();
    private float m_lastCheckTime = 0;
    private readonly int CHECKCD = 120;

    public override void OnInit()
    {
        base.OnInit();
        m_lastCheckTime = Time.unscaledTime;
    }
    public override void OnReset()
    {
        base.OnReset();
        ReleaseAllPool();
    }
    public void AddToPoolList(IPool pool)
    {
        m_poolList.Add(pool);
    }
    private void ReleaseAllPool()
    {
        for (int i = 0; i < m_poolList.Count; i++)
        {
            IPool pool = m_poolList[i];
            if (pool == null)
            {
                continue;
            }
            pool.DestroyPool();
            pool = null;
        }
        m_poolList.Clear();
    }
    public void OnSlowUpdate(float deltaTime)
    {
        if (Time.unscaledTime - m_lastCheckTime < CHECKCD)
        {
            return;
        }
        for (int i = 0; i < m_poolList.Count; i++)
        {
            IPool pool = m_poolList[i];
            if (pool == null)
            {
                continue;
            }
            pool.CheckShrink();
        }
        m_lastCheckTime = Time.unscaledTime;
    }
}

public class ObjectPool<T> : IPool where T : class
{
    private readonly Stack<T> m_Stack = new Stack<T>();
    private readonly Action<T> m_ActionOnGet;
    private readonly Action<T> m_ActionOnRelease;
    private readonly Func<T> m_crtor;
    private readonly Action<T> m_ActionOnDesrtoy;

    private float m_LastReleaseTime = 0;
    private bool m_bCheckTime = true;
    /// <summary>
    /// 收縮時最小的对象总数量
    /// </summary>
    private readonly int SHRINKMINTOTAL = 10;
    /// <summary>
    /// 收缩点
    /// </summary>
    private readonly float SHRINKPOINT = 0.2f;

    public int countAll
    {
        get;
        private set;
    }

    public int countActive
    {
        get {
            return this.countAll - this.countInactive;
        }
    }

    public int countInactive
    {
        get {
            return this.m_Stack.Count;
        }
    }

    public ObjectPool(Action<T> actionOnGet, Action<T> actionOnRelease, Func<T> crtor = null, Action<T> actionOnDestroy = null, bool bcheck = true)
    {
        this.m_ActionOnGet = actionOnGet;
        this.m_ActionOnRelease = actionOnRelease;
        this.m_crtor = crtor;
        this.m_ActionOnDesrtoy = actionOnDestroy;
        this.m_bCheckTime = bcheck;
        ObjectPoolManager.Ins.AddToPoolList(this);
    }

    public T Get()
    {
        T t;
        if (this.m_Stack.Count == 0)
        {
            if (m_crtor != null)
            {
                t = m_crtor();
            }
            else
            {
                t = (default(T) == null) ? Activator.CreateInstance<T>() : default(T);
            }
            this.countAll++;
        }
        else
        {
            t = this.m_Stack.Pop();
        }
        if (this.m_ActionOnGet != null)
        {
            this.m_ActionOnGet.Invoke(t);
        }
        return t;
    }

    public void Release(T element)
    {
        if (element == null)
            return;
        if (this.m_Stack.Count > 0 && object.ReferenceEquals(this.m_Stack.Peek(), element))
        {
            Debug.LogError("Internal error. Trying to destroy object that is already released to pool.");
        }
        if (this.m_ActionOnRelease != null)
        {
            this.m_ActionOnRelease.Invoke(element);
        }
        this.m_Stack.Push(element);
        if (m_bCheckTime)
        {
            if (Time.time != m_LastReleaseTime)
                CheckShrink();
            m_LastReleaseTime = Time.time;
        }
    }

    public void DestroyPool()
    {
        if (m_Stack.Count == 0)
        {
            return;
        }
        do
        {
            DestroyOneObject();
        }
        while (m_Stack.Count > 0);
        m_Stack.Clear();
    }
    private void DestroyOneObject()
    {
        T t = m_Stack.Pop();
        if (m_ActionOnDesrtoy != null)
        {
            m_ActionOnDesrtoy(t);
        }

        var go = t as GameObject;
        if (go != null)
        {
            GameObject.Destroy(go);
        }
        this.countAll--;
    }

    public void CheckShrink()
    {
        if (countAll == 0)
        {
            return;
        }
        if (countAll > SHRINKMINTOTAL && countActive / (float)countAll < SHRINKPOINT)
        {
            int num = m_Stack.Count / 2;
            while (m_Stack.Count > num)
            {
                DestroyOneObject();
            }
        }
    }
}